import java.awt.Frame;



import java.io.*;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import static org.junit.Assert.fail;
import org.junit.*;

import javax.swing.JOptionPane;

/**
 * Class to create .ics files from RFC2445 specifications.
 */

public class Creation {
	
	protected Frame frame = new Frame();
	protected String version = "VERSION:2.0"; //sets calendar version to 2.0
	protected String classification = "CLASS:PUBLIC"; //default classification is PUBLIC
	protected String comment = null; //holds comment here
	protected String geoPosition = null; //holds geographical position here
	protected String location = null; //holds location of event
	protected String resource = null; //holds string list of resources
	protected Date startDate = null; //used to compare to end date
	protected Date startTime = null; //used to compare to end time
	protected Date endDate = null; //used to compare to start date
	protected Date endTime = null; //used to compare to start time
	protected String dtStart = null; //holds final start date-time formated string
	protected String dtEnd = null; //holds final end date-time formatted string
	protected String timezone = null; //holds timezone identifier
	protected String dtStamp = null; //needed for events
	
	

	/*
	 * sets version string as PUBLIC, PRIVATE or CONFIDENTIAL
	 * Parameter will take ints from 0 to 2
	 * PUBLIC = 0, PRIVATE = 1, CONFIDENTIAL = 2
	 */
	public void classification(int state) {
		if (state == 0) this.classification = "CLASS:PUBLIC";
		else if (state == 1) this.classification = "CLASS:PRIVATE";
		else if (state == 2) this.classification = "CLASS:CONFIDENTIAL";
	}
	
	
	//this method inputs a comment into the .ics file and takes a String as the comment to be entered
	public void comment(String comment) {
		if (comment == null);
		else if (comment != null) this.comment = "COMMENT:"+comment; //concatenates String comment onto "COMMENT:"
	}
	

	//this method sets geographical positions by taking float values of latitude and longitude
	public void geoPosition(float latitude, float longitude) {
		if (latitude > 90.000000f || latitude < -90.000000f) {
			geoPosition = null;
        	JOptionPane.showMessageDialog(frame, "Latitude is out of bounds!");
		} else if (longitude > 180.000000f || longitude < -180.000000f) {
			geoPosition = null;
        	JOptionPane.showMessageDialog(frame, "Longitude is out of bounds!");
		} else if ((latitude <= 90.000000f && latitude >= -90.000000f) && (longitude <= 180.000000f && longitude >= -180.000000f)) {
			float temp = latitude;
			float temp2 = longitude;
			latitude = (float) ((1000000 * temp) / 1000000);
			longitude = (float) ((1000000 * temp2) / 1000000);
			geoPosition = "GEO:" + latitude + ";" + longitude;
		} else {
			geoPosition = null;
        	JOptionPane.showMessageDialog(frame, "Error: Geographical Positioning.");
		}
		
	}
	

	
	//creates location and URIs if given by the user
	public void location(String loc, String uri) throws MalformedURLException {
		if (loc == null) {
			//do nothing
		}else if (loc != null) {
			this.location = "LOCATION:" + loc;
		}
		//adds URI if detected and checks of validity 
		if(uri != null) {
			Pattern uriPattern = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
			Matcher validate = uriPattern.matcher(uri);
			//if URI is valid the LOCATION is changed to include URI
			if (validate.matches() && loc != null) this.location = "LOCATION;ALTREP=" + "\"" + uri + "\"" + ":" + loc;
			else if (validate.matches() && loc == null) this.location = "LOCATION;ALTREP=" + "\"" + uri + "\"" + ":";
			else JOptionPane.showMessageDialog(frame, "Error: Bad URI syntax.");
		}

	}
	
	
	/*
	 * accepts list of resources separated by comma
	 * may be implemented using an array or some collection
	 */
	public void resources(String res) {
		if (res == null) {
			//do nothing
		} else if (res != null) {
			this.resource = "RESOURCES:" + res;
		}
	}
	
	

	
	//sets date and time for event start
	public void dtStart(int month, int day, int year, int hour, int min, int sec) {
		//tests if date is valid
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
			sdf.setLenient(false);
			startDate = sdf.parse(month + "/" + day + "/" + year);
        } catch (ParseException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Starting Date!.");
        	dtStart = null; //resets value
        } catch (IllegalArgumentException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Starting Date!.");
        	dtStart = null; //resets value
        }
		//tests if time is valid (hours are 0 to 23 in value)
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
			sdf.setLenient(false);
			startTime = sdf.parse(hour + ":" + min + ":" + sec);
			
			//creating DTSTART string here
			dtStart = "DTSTART:" + year;
			
			if (month < 10) dtStart = dtStart + "0" + month;
			else dtStart = dtStart + month;
			
			if (day < 10) dtStart = dtStart + "0" + day;
			else dtStart = dtStart + day;
			
			dtStart = dtStart + "T";
			
			if(hour < 10) dtStart = dtStart + "0" + hour;
			else dtStart = dtStart + hour;
			
			if(min < 10) dtStart = dtStart + "0" + min;
			else dtStart = dtStart + min;
			
			if(sec < 10) dtStart = dtStart + "0" + sec;
			else dtStart = dtStart + sec;
			
			//dtStart = dtStart + "Z";
			
        } catch (ParseException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Starting Time!.");
        	dtStart = null; //resets value
        } catch (IllegalArgumentException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Starting Time!.");
        	dtStart = null; //resets value
        }
		

	}
	

	//sets end time for event but checks that start time exists and that it is after the start time
	public void dtEnd(int month, int day, int year, int hour, int min, int sec) {
		//tests if date is valid
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
			sdf.setLenient(false);
			endDate = sdf.parse(month + "/" + day + "/" + year);
        } catch (ParseException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Ending Date!.");
        	dtEnd = null; //resets value
        } catch (IllegalArgumentException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Ending Date!.");
        	dtEnd = null; //resets value
        }
		//tests if time is valid
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
			sdf.setLenient(false);
			endTime = sdf.parse(hour + ":" + min + ":" + sec);
			
			//checks if start date-time is before end date-time
			if (startTime.compareTo(endTime) < 0 && startDate.compareTo(endDate) <= 0) {
				//creating DTSTART string here
				dtEnd = "DTEND:" + year;
				
				if (month < 10) dtEnd = dtEnd + "0" + month;
				else dtEnd = dtEnd + month;
				
				if (day < 10) dtEnd = dtEnd + "0" + day;
				else dtEnd = dtEnd + day;
				
				dtEnd = dtEnd + "T";
				
				if(hour < 10) dtEnd = dtEnd + "0" + hour;
				else dtEnd = dtEnd + hour;
				
				if(min < 10) dtEnd = dtEnd + "0" + min;
				else dtEnd = dtEnd + min;
				
				if(sec < 10) dtEnd = dtEnd + "0" + sec;
				else dtEnd = dtEnd + sec;
				
				//dtEnd = dtEnd + "Z";
				
			} else {
	        	JOptionPane.showMessageDialog(frame, "Ending time is before starting time!.");
				dtEnd = null; //if time is wrong DTEND is not added
			}
			
        } catch (ParseException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Ending Time!.");
        	dtEnd = null; //resets value
        } catch (IllegalArgumentException e) {
        	JOptionPane.showMessageDialog(frame, "Invalid Ending Time!.");
        	dtEnd = null; //resets value
        }
	}

	
	//gets timezone and returns GMT if user enters invalid timezone
	public void timeZoneId(String id) {
		timezone = "TZID:" + TimeZone.getTimeZone(id).getID();
	}
	
	//used to time stamp calendar file and is needed for VEVENT
	public void dtStamp() {
		DateFormat currentDate = new SimpleDateFormat("yyyyddMM");
		DateFormat currentTime = new SimpleDateFormat("HHmmss");
		Date date = new Date();
		dtStamp = "DTSTAMP:" + currentDate.format(date) + "T" + currentTime.format(date) + "Z";
	}
	
	public void createFile() throws IOException{
		FileWriter stream = null;
		BufferedWriter output = null;
		
		File file = new File("calendar.ics");
		  if(!file.exists()){
			  file.createNewFile();
			  stream = new FileWriter("calendar.ics");
			  output = new BufferedWriter(stream);
			  
			  output.write("BEGIN:VCALENDAR" + "\n"); //writes BEGIN:VCALENDAR, required for valid file
			  
			  if (classification != null) output.write(classification + "\n"); //writes CLASS:PUBLIC, CLASS:PRIVATE or CLASS:CONFIDENTIAL
			  
			  output.write("METHOD:PUBLISH" + "\n"); //writes METHOD:PUBLISH
			  
			  output.write("BEGIN:VTIMEZONE" + "\n"); //writes BEGIN:VTIMEZONE required for timezone identifier
			  
			  output.write(timezone + "\n"); //writes TZID
			  
			  output.write("END:VTIMEZONE" + "\n"); //Writes END:VTIMEZONE
			  
			  output.write(version + "\n"); //writes VERSION:2.0
			  
			  output.write("BEGIN:VEVENT" + "\n"); //writes BEGIN:EVENT
			  			  
			  if (comment != null) output.write(comment + "\n"); //writes classification to .ics file if value is not null
			  
			  if (geoPosition != null) output.write(geoPosition + "\n"); //writes geographical position in event file
			  
			  if (location != null) output.write(location + "\n"); //writes location in event file
			  
			  output.write(resource + "\n");
			  
			  output.write(dtStamp + "\n"); //writes DTSTAMP date and time
			  			  			  
			  if (dtStart == null) { //if dtStart doesn't exist then file with cancel creation
				  output.flush();
				  output.close();
				  JOptionPane.showMessageDialog(frame, "Aborting: Start Date-Time Invalid!.");
			  } else output.write(dtStart + "\n"); //writes start time of event

			  if (dtEnd != null) output.write(dtEnd + "\n"); //writes end time of event if there is one
			  
			  output.write("END:VEVENT" + "\n");
			  output.write("END:VCALENDAR");
			  output.close();
		  }
	}
	
	
	
	/**
	 * Classification Test cases
	 * 
	 * Tests to see if the valid classification inputs execute
	 * properly and also checks if an invalid input handles
	 * the error properly
	 */
	
	@Test
	public void classificationTest(){
		classification(0);
		if (!(classification.equals("CLASS:PUBLIC")))
			fail("Public Classification failed");
	}
	
	@Test
	public void privateClassificationTest(){
		classification(1);
		if (!((this.classification).equals("CLASS:PRIVATE")))
			fail("Private Classification failed");
	}
	
	@Test
	public void confClassificationTest(){
		classification(2);
		if (!((this.classification).equals("CLASS:CONFIDENTIAL")))
			fail("Confidential Classification failed"); 
	}
	
	
	@Test
	public void invalidClassifTest(){
		classification(3);
		if (!((this.classification).equals("CLASS:PUBLIC")))
			fail("Invalid Classification failed"); 
	}
	
	/**
	 * Comment tests
	 * 
	 * Tests to see if a null comment is handled properly by not adding
	 * anything to the comment field.  Also tests to see if adding
	 * a comment removes the null default.
	 */
	
	@Test
	public void nullComment(){
		comment(null);
		if (comment != null)
			fail("null comment failed");
	}
	
	@Test
	public void validComment(){
		comment("This is a comment");
		if (comment == null)
			fail("valid comment failed");
	}
	
	/**
	 * Geographic position tests
	 * 
	 * Tests to see if the combinations of invalid longitude and
	 * latitude inputs are caught by the geoPosition function.  
	 * Also tests to see if a valid input removes the null default
	 */
  
	@Test
	public void LatOutOfBTest(){
		geoPosition(91f, 83f);
		if (geoPosition != null)
			fail("Maximum Lat exceeding fail");
		geoPosition(-91f, 56f);
		if(geoPosition != null)
			fail("Minimum Lat exceeding fail");
	}
	
	@Test
	public void LonOutOfBTest(){
		geoPosition(-35f, 181f);
		if (geoPosition != null)
			fail("Maximum Lon exceeding fail");
		geoPosition(52f, -181f);
		if(geoPosition != null)
			fail("Minimum Lon exceeding fail");
	}
	
	@Test
	public void validPositionTest(){
		geoPosition(25f, 150f);
		if (geoPosition == null)
			fail("pos lat, pos long fail");
		geoPosition(-45f, 26f);
		if(geoPosition == null)
			fail("neg lat, pos long fail");
		geoPosition(75f, -134f);
		if(geoPosition == null)
			fail("pos lat, neg long fail");
		geoPosition(-82f, -16f);
		if(geoPosition == null)
			fail("neg lat, neg longfail");
	}
	
	/**
	 * Location and URI tests
	 * 
	 * Tests to see if a null location will get caught.
	 * Could not test with just a null location and a valid
	 * url since it will add the url to the location field if
	 * the url is valid.  
	 * 
	 * Also tested to see that valid location and url worked too
	 */
	
	@Test
	public void nullLocTest(){
		try{
		location(null, null);
		} catch (MalformedURLException murle){
			System.out.println("bad url");
		}
		if (location != null)
			fail("null location and null uri fail");
	}
	
	@Test
	public void validLocTest(){
		try{
		location("UH Manoa", "https://www.hawaii.edu");
		} catch (MalformedURLException murle){
			System.out.println("bad url");
		}
		if (location == null)
			fail("valid location and valid uri fail");
	}
	
	/**
	 * Resources Tests
	 * 
	 * Tested to see if adding a null resource would keep the 
	 * resource field null as well as adding a valid resource 
	 * and checking to see if the null field was removed
	 */
	
	@Test
	public void nullResourceTest(){
		resources(null);
		if (resource != null)
			fail("null resource fail");
	}
	
	@Test
	public void validResourceTest(){
		resources("Example Resource");
		if (resource == null)
			fail("valid resource fail");
	}
	
	/**
	 * Start and end time/date tests
	 * 
	 * Tested to see if all the fields of
	 * month, day, year, hour, min, sec 
	 * accepted valid and rejected invalid
	 * inputs. Repeated the tests for both 
	 * the end and start time/dates
	 */
	
	@Test 
	public void validDateTimeTest(){
		dtStart(12, 25, 2012, 0, 0, 0);
		if(startDate == null)
			fail("valid date failed");
		if(startTime == null)
			fail("valid time failed");
	}
	
	@Test
	public void invalidMonthTest(){
		dtStart(13, 1, 2012, 0, 0, 0);
		if(startDate != null)
			fail("Invalid Month fail");
		dtStart(-13, 1, 2012, 0, 0, 0);
		if(startDate != null)
			fail("Invalid Neg Month fail");
	}
	
	@Test 
	public void invalidDayTest(){
		dtStart(12, 33, 2012, 0, 0, 0);
		if(startDate != null)
			fail("Invalid Day fail");
		dtStart(12, -33, 2012, 0, 0, 0);
		if(startDate != null)
			fail("Invalid Neg Day fail");
	}
	
	@Test
	public void invalidYearTest(){
		dtStart(12, 1, -2012, 0, 0, 0);
		if(startDate != null)
			fail("Invalid Year fail");
	}
	
	@Test
	public void invalidHourTest(){
		dtStart(12, 1, 2012, 25, 30, 30);
		if(startTime != null)
			fail("Invalid Hour fail");
		dtStart(12, 1, 2012, -25, 30, 30);
		if(startTime != null)
			fail("Invalid Neg Hour fail"); 
	}
	
	@Test
	public void invalidMinTest(){
		dtStart(12, 1, 2012, 2, 68, 0);
		if(startTime != null)
			fail("Invalid Min fail");
		dtStart(12, 1, 2012, 2, -68, 0);
		if(startTime != null)
			fail("Invalid Neg Min fail");
	}
	
	@Test
	public void invalidSecTest(){
		dtStart(12, 1, 2012, 2, 25, 75);
		if(startTime != null)
			fail("Invalid Sec fail");
		dtStart(12, 1, 2012, 2, 25, -75);
		if(startTime != null)
			fail("Invalid Neg Sec fail");
	}
	
	@Test 
	public void validEndDateTimeTest(){
		dtStart(12, 25, 2012, 0, 0, 0);
		dtEnd(12, 26, 2012, 22, 59, 59);
		if(endDate == null)
			fail("valid date failed");
		if(endTime == null)
			fail("valid time failed");
	}
	
	@Test
	public void invalidEndMonthTest(){
		dtStart(12, 25, 2012, 0, 0, 0);
		dtEnd(13, 1, 2012, 0, 0, 0);
		if(endDate != null)
			fail("Invalid Month fail");
		dtEnd(-13, 1, 2012, 0, 0, 0);
		if(endDate != null)
			fail("Invalid Neg Month fail");
	}
	
	@Test 
	public void invalidEndDayTest(){
		dtStart(12, 25, 2012, 0, 0, 0);
		dtEnd(12, 33, 2012, 0, 0, 0);
		if(endDate != null)
			fail("Invalid Day fail");
		dtEnd(12, -33, 2012, 0, 0, 0);
		if(endDate != null)
			fail("Invalid Neg Day fail");
	}
	
	@Test
	public void invalidEndYearTest(){
		dtStart(12, 25, 2012, 0, 0, 0);
		dtEnd(12, 1, -2012, 0, 0, 0);
		if(endDate != null)
			fail("Invalid Year fail");
	}
	
	@Test
	public void invalidEndHourTest(){
		dtEnd(12, 1, 2012, 25, 30, 30);
		if(endTime != null)
			fail("Invalid Hour fail");
		dtEnd(12, 1, 2012, -25, 30, 30);
		if(endTime != null)
			fail("Invalid Neg Hour fail"); 
	}
	
	@Test
	public void invalidEndMinTest(){
		dtEnd(12, 1, 2012, 2, 68, 0);
		if(endTime != null)
			fail("Invalid Min fail");
		dtEnd(12, 1, 2012, 2, -68, 0);
		if(endTime != null)
			fail("Invalid Neg Min fail");
	}
	
	@Test
	public void invalidEndSecTest(){
		dtEnd(12, 1, 2012, 2, 25, 75);
		if(endTime != null)
			fail("Invalid Sec fail");
		dtEnd(12, 1, 2012, 2, 25, -75);
		if(endTime != null)
			fail("Invalid Neg Sec fail");
	}
	
	
	/**
	 * Timezone Tests
	 * 
	 * Tested to see if an invalid timezone id would 
	 * set the timezone to the default GMT and if a valid
	 * one would set it to the right one
	 */
	
	@Test
	public void invalidTimezoneTest(){
		timeZoneId("test");
		if(!(timezone.equals("TZID:GMT")))
			fail("Invalid Timezone fail");
	}
	
	@Test
	public void validTimezoneTest(){
		timeZoneId("HST");
		if(!(timezone.equals("TZID:HST")))
			fail("Valid Timezone fail");
	}
	
	/**
	 * Use this to test or understand the code better
	public static void main(String[] args) throws IOException {
		Creation export = new Creation(); //test object
		
		export.classification(0); //sets classification to PUBLIC
		export.timeZoneId("HST");
		export.comment("This is far from done!"); //sets a comment
		export.geoPosition((float)89.11111111, (float)-179.11111111);
		export.location("UH", "https://www.google.com/");
		export.dtStart(4, 29, 2012, 22, 0, 0);
		export.dtEnd(4, 29, 2012, 23, 0, 0);
		export.dtStamp();
		export.createFile(); //tests createFile() method
	}
	*/
	
	
}
